• Steven Ponce
  • About
  • Data Visualizations
  • Projects
  • Resume
  • Email

On this page

  • Original
  • Makeover
  • Steps to Create this Graphic
    • 1. Load Packages & Setup
    • 2. Read in the Data
    • 3. Examine the Data
    • 4. Tidy Data
    • 5. Visualization Parameters
    • 6. Plot
    • 7. Save
    • 8. Session Info
    • 9. GitHub Repository
    • 10. References

TrueNut’s Market Dominance in Powdered Nut Butter

  • Show All Code
  • Hide All Code

  • View Source

Strategic position and category performance across a $386M market

SWDchallenge
Exercise
Data Visualization
R Programming
2025
A strategic market analysis visualizing TrueNut’s dominant position in the powdered nut butter market. Using complementary quadrant and dumbbell charts, this visualization reveals how TrueNut commands 70% market share with complete category coverage, while competitors NutBrite and GoldenSpread occupy specialist and limited player positions respectively.
Author

Steven Ponce

Published

February 27, 2025

Original

The goal of this month’s Storytelling with Data exercise is to go crazy or keep it simple (marimekko chart).

Figure 1: Original chart

Additional information can be found HERE

Makeover

Figure 2: A dual-panel visualization titled TrueNut’s Market Dominance in Powdered Nut Butter. The left panel shows a quadrant chart positioning TrueNut as a market leader with 100% category coverage and high market share, NutBrite as a specialist with high coverage but lower share, and GoldenSpread as a limited player with low coverage and share. The right panel displays a dumbbell chart showing sales by product category, with TrueNut leading in most categories, particularly in Peanut Butter ($71.9M) and Hazelnut Spread ($73.81M).

Steps to Create this Graphic

1. Load Packages & Setup

Show code
if (!require("pacman")) install.packages("pacman")
pacman::p_load(
  tidyverse,         # Easily Install and Load the 'Tidyverse'
  ggtext,            # Improved Text Rendering Support for 'ggplot2'
  showtext,          # Using Fonts More Easily in R Graphs
  scales,            # Scale Functions for Visualization
  glue,              # Interpreted String Literals
  here,              # A Simpler Way to Find Your Files
  janitor,           # Simple Tools for Examining and Cleaning Dirty Data
  camcorder,         # Record Your Plot History,
  paletteer,         # Comprehensive Collection of Color Palettes
  patchwork          # The Composer of Plots # The Composer of Plots # The Composer of Plots
) 

### |- figure size ---- 
gg_record( 
  dir    = here::here("temp_plots"), 
  device = "png",
  width  = 12,
  height = 8,
  units  = "in",
  dpi    = 320)
# Source utility functions
suppressMessages(source(here::here("R/utils/fonts.R")))
source(here::here("R/utils/social_icons.R"))
source(here::here("R/utils/image_utils.R"))
source(here::here("R/themes/base_theme.R"))

2. Read in the Data

Show code
raw_data <- read_csv(
  here::here("data/swdexercise055.csv")
  ) |> 
  clean_names() 

3. Examine the Data

Show code
glimpse(raw_data)

4. Tidy Data

Show code
# P1. Market Share Data ----

# Transform raw data to long format with company identifiers
long_data <- raw_data |>
  # Remove the total market row
  filter(x1 != "Total Market") |>
  # Convert from wide to long format
  pivot_longer(
    cols = c(true_nut_sales, nut_brite_sales, golden_spread_sales),
    names_to = "company", 
    values_to = "sales"
  ) |>
  # Clean company names and calculate percentages
  mutate(
    # Use case_when instead of recode for better readability
    company = case_when(
      company == "true_nut_sales" ~ "TrueNut",
      company == "nut_brite_sales" ~ "NutBrite",
      company == "golden_spread_sales" ~ "GoldenSpread",
      TRUE ~ company  # Fallback for unexpected values
    ),
    # Calculate percentage of category total
    percentage = sales / total_sales * 100
  )

# Calculate overall company totals
company_totals <- raw_data |>
  # Sum sales for each company
  summarise(
    TrueNut = sum(true_nut_sales),
    NutBrite = sum(nut_brite_sales),
    GoldenSpread = sum(golden_spread_sales)
  ) |>
  # Convert to long format
  pivot_longer(
    cols = everything(), 
    names_to = "company", 
    values_to = "sales"
  ) |>
  # Calculate market share percentages
  mutate(percentage = sales / sum(sales) * 100)

# Calculate category coverage for each company
coverage <- long_data |>
  # Only count categories where the company has sales
  filter(sales > 0) |>
  # Count unique categories per company
  group_by(company) |>
  summarise(
    categories_covered = n_distinct(x1),
    # Calculate as percentage of all categories
    coverage_percent = categories_covered / n_distinct(long_data$x1) * 100
  )

# Join company totals with coverage data
position_data <- left_join(company_totals, coverage, by = "company")

# Define quadrant labels with semantic positioning
quadrant_labels <- tibble(
  # Define quadrant centers
  quadrant = c("Specialists", "Limited Players", "Market Leaders", "Volume Players"),
  x = c(25, 25, 75, 75),     
  y = c(65, 20, 65, 20),
  # Add clear descriptions
  description = c(
    "(Niche categories, strong coverage)",
    "(Low share, few categories)",
    "(Strong share, wide coverage)",
    "(High share, category focused)"
  )
) |>
  # Convert to label format expected by ggplot
  rename(label = quadrant)

# P2. Market by Product Category Data ----

# Calculate category statistics and establish ordering
category_stats <- long_data |> 
  # Group by category
  group_by(x1) |>
  # Get key category metrics (just once per category)
  summarize(
    total_sales = first(total_sales),
    .groups = "drop"  
  ) |>
  # Sort by total sales descending
  arrange(desc(total_sales))

# Create ordered factor for consistent category display
category_order <- category_stats |> pull(x1)

# Prepare the main plotting data with ordered categories
plot_data <- long_data |>
  # Create ordered factor with categories in descending sales order
  mutate(
    # Reverse for bottom-to-top ordering in the plot
    x1 = factor(x1, levels = rev(category_order))
  ) |>
  # Only include meaningful sales values
  filter(sales > 0.01)  

# Create the label data from filtered plot data
label_data <- plot_data |> 
  # Format sales values as currency with millions indicator
  mutate(
    label = paste0("$", round(sales, 2), "M")
  )

# Simplified category totals reference (using earlier calculation)
category_totals <- category_stats |>
  # Apply the same factor ordering
  mutate(x1 = factor(x1, levels = rev(category_order)))

5. Visualization Parameters

Show code
### |-  plot aesthetics ----
# Get base colors with custom palette
colors <- get_theme_colors(
  palette = paletteer:::paletteer_d("ltc::trio4")
)

### |-  titles and caption ----
title_text <- str_glue("TrueNut's Market Dominance in Powdered Nut Butter")

subtitle_text <- str_glue("Strategic position and category performance across a $386M market")

# Create caption
caption_text <- create_swd_exe_caption(
  year = 2025,
  month = "Feb",
  source_text =  "Let's Practice! Exercise 5.5"
)

### |-  fonts ----
setup_fonts()
fonts <- get_font_families()

### |-  plot theme ----

# Start with base theme
base_theme <- create_base_theme(colors)

# Add weekly-specific theme elements
weekly_theme <- extend_weekly_theme(
  base_theme,
  theme(
    # Text styling 
    plot.title = element_text(face = "bold", family = fonts$title, size = rel(1.14), margin = margin(b = 10)),
    plot.subtitle = element_text(family = fonts$subtitle, color = colors$text, size = rel(0.78), margin = margin(b = 20)),
    
    # Axis elements
    axis.title = element_text(color = colors$text, size = rel(0.8)),
    axis.text = element_text(color = colors$text, size = rel(0.7)),
    
    # Grid elements
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "grey95", linewidth = 0.1),
    
    # Legend elements
    legend.position = "plot",
    legend.title = element_text(family = fonts$text, size = rel(0.8)),
    legend.text = element_text(family = fonts$text, size = rel(0.7)),
    
    # Plot margins 
    plot.margin = margin(t = 10, r = 10, b = 10, l = 10),
  )
)

# Set theme
theme_set(weekly_theme)

6. Plot

Show code
# P1. Market Position Chart ----
p1 <- ggplot(position_data, aes(x = percentage, y = coverage_percent)) +
  # Geoms
  geom_rect(xmin = 0, xmax = 50, ymin = 0, ymax = 50, fill = "gray95", alpha = 0.5) +
  geom_rect(xmin = 50, xmax = 100, ymin = 0, ymax = 50, fill = "gray95", alpha = 0.5) +
  geom_rect(xmin = 0, xmax = 50, ymin = 50, ymax = 100, fill = "gray95", alpha = 0.5) +
  geom_rect(xmin = 50, xmax = 100, ymin = 50, ymax = 100, fill = "gray95", alpha = 0.5) +
  geom_hline(yintercept = 50, linetype = "dashed", color = "gray50") +
  geom_vline(xintercept = 50, linetype = "dashed", color = "gray50") +
  geom_point(aes(color = company), size = 6, alpha = 0.8) +
  geom_text(aes(label = company), color = "gray20", 
            fontface = "bold", size = 3.5, vjust = 3) +
  geom_text(
    data = quadrant_labels,
    aes(x = x, y = y, label = label),
    size = 4,
    color = "gray40",
    fontface = "bold",
    hjust = 0.5,
    vjust = 0.5
  ) +
  geom_text(
    data = quadrant_labels,
    aes(x = x, y = y - 4, label = description),  
    size = 3,
    color = "gray50",
    fontface = "italic",
    hjust = 0.5,
    vjust = 0.5
  ) +
  # Scales
  scale_x_continuous(
    labels = percent_format(scale = 1), limits = c(0, 100)
    ) +
  scale_y_continuous(
    labels = percent_format(scale = 1), limits = c(0, 100)
    ) +
  scale_color_manual(values = colors$palette) +
  coord_cartesian(clip = "off") +
  # Labs  
  labs(
    title = "Company Market Position Analysis",
    subtitle = "Comparison of market share vs. category coverage",
    x = "Market Share\n(% of total sales)",
    y = "Category Coverage\n(% of product categories)"
  ) 

# P2. Market by Product Category Chart ----
p2 <- ggplot() +
  # Geoms
  geom_segment(
    data = category_totals,
    aes(y = x1, yend = x1, x = 0, xend = max(total_sales) * 1.05),
    color = "gray85", linewidth = 0.5
  ) +
  geom_point(
    data = plot_data,
    aes(x = sales, y = x1, color = company),
    size = 4, alpha = 0.9,
    show.legend = TRUE
  ) +
  geom_text(
    data = label_data,
    aes(x = sales, y = x1, label = label),
    vjust = -0.9, 
    size = 3,
    show.legend = FALSE  
  ) +
  geom_text(
    data = category_totals,
    aes(x = max(total_sales) * 1.1, y = x1, label = paste0("Total: $", total_sales, "M")),
    hjust = 0, vjust = 0.3, size = 3, color = "gray30",
    show.legend = FALSE
  ) +
  # Scales
  scale_x_continuous(
    labels = dollar_format(suffix = "M"),
    limits = c(-10, max(category_totals$total_sales) * 1.3),
    expand = c(0.01, 0)
  ) +
  scale_color_manual(values = colors$palette) +
  # Labs
  labs(
    title = "Nut Butter Market Analysis by Product Category",
    subtitle = "Sales comparison across product categories by company",
    x = "Sales ($ Millions)",
    y = NULL,
  ) +
  # Theme
  theme(
    legend.position = "top",
    legend.title = element_blank(),
    legend.key.size = unit(0.8, "lines"),  
  )

# Combined Charts -----

# Combined Plot -----
combined_plot <- (p1 + p2) +
  plot_layout(widths = c(1, 1))   

combined_plot <- combined_plot +
  plot_annotation(
    title = title_text,
    subtitle = subtitle_text,
    caption = caption_text,
    theme = theme(
      plot.title = element_text( 
        size = rel(2.2),
        family = fonts$title,
        face = "bold",
        color = colors$title,
        lineheight = 1.1,
        margin = margin(t = 5, b = 5)
      ),
      plot.subtitle = element_text(
        size = rel(0.95),  
        family = fonts$subtitle,
        color = colors$subtitle,
        lineheight = 1.2,
        margin = margin(t = 5, b = 10)
      ),
      plot.caption = element_markdown(
        size   = rel(0.65),
        family = fonts$caption,
        color  = colors$caption,
        hjust  = 0.5,
        margin = margin(t = 10)
      ),
      plot.margin = margin(t = 20, r = 10, b = 20, l = 10),
    ))

7. Save

Show code
### |-  plot image ----  

source(here::here("R/image_utils.R"))
save_plot_patchwork(
  combined_plot, type = 'swd', year = 2025, month = 02, exercise = 055,
  width = 12, height = 8
  )

8. Session Info

Expand for Session Info
R version 4.4.1 (2024-06-14 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 22631)

Matrix products: default


locale:
[1] LC_COLLATE=English_United States.utf8 
[2] LC_CTYPE=English_United States.utf8   
[3] LC_MONETARY=English_United States.utf8
[4] LC_NUMERIC=C                          
[5] LC_TIME=English_United States.utf8    

time zone: America/New_York
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices datasets  utils     methods   base     

other attached packages:
 [1] patchwork_1.3.0 paletteer_1.6.0 camcorder_0.1.0 janitor_2.2.0  
 [5] here_1.0.1      glue_1.8.0      scales_1.3.0    showtext_0.9-7 
 [9] showtextdb_3.0  sysfonts_0.8.9  ggtext_0.1.2    lubridate_1.9.3
[13] forcats_1.0.0   stringr_1.5.1   dplyr_1.1.4     purrr_1.0.2    
[17] readr_2.1.5     tidyr_1.3.1     tibble_3.2.1    ggplot2_3.5.1  
[21] tidyverse_2.0.0 pacman_0.5.1   

loaded via a namespace (and not attached):
 [1] gtable_0.3.6       xfun_0.49          htmlwidgets_1.6.4  tzdb_0.4.0        
 [5] yulab.utils_0.1.8  vctrs_0.6.5        tools_4.4.0        generics_0.1.3    
 [9] curl_6.0.0         parallel_4.4.0     gifski_1.32.0-1    fansi_1.0.6       
[13] pkgconfig_2.0.3    ggplotify_0.1.2    lifecycle_1.0.4    compiler_4.4.0    
[17] farver_2.1.2       munsell_0.5.1      codetools_0.2-20   snakecase_0.11.1  
[21] htmltools_0.5.8.1  yaml_2.3.10        crayon_1.5.3       pillar_1.9.0      
[25] magick_2.8.5       commonmark_1.9.2   tidyselect_1.2.1   digest_0.6.37     
[29] stringi_1.8.4      rematch2_2.1.2     labeling_0.4.3     rsvg_2.6.1        
[33] rprojroot_2.0.4    fastmap_1.2.0      grid_4.4.0         colorspace_2.1-1  
[37] cli_3.6.3          magrittr_2.0.3     utf8_1.2.4         withr_3.0.2       
[41] bit64_4.5.2        timechange_0.3.0   rmarkdown_2.29     bit_4.5.0         
[45] hms_1.1.3          evaluate_1.0.1     knitr_1.49         markdown_1.13     
[49] gridGraphics_0.5-1 rlang_1.1.4        gridtext_0.1.5     Rcpp_1.0.13-1     
[53] xml2_1.3.6         renv_1.0.3         svglite_2.1.3      rstudioapi_0.17.1 
[57] vroom_1.6.5        jsonlite_1.8.9     R6_2.5.1           fs_1.6.5          
[61] prismatic_1.1.2    systemfonts_1.1.0 

9. GitHub Repository

Expand for GitHub Repo

The complete code for this analysis is available in swd_2025_02-Ex_055.qmd. For the full repository, click here.

10. References

Expand for References

Data Sources:

  1. Data Sources:

    • Storytelling with Data Exercise | go crazy or keep it simple: Download the data
Back to top
Source Code
---
title: "TrueNut's Market Dominance in Powdered Nut Butter"
subtitle: "Strategic position and category performance across a $386M market"
description: "A strategic market analysis visualizing TrueNut's dominant position in the powdered nut butter market. Using complementary quadrant and dumbbell charts, this visualization reveals how TrueNut commands 70% market share with complete category coverage, while competitors NutBrite and GoldenSpread occupy specialist and limited player positions respectively."
author: "Steven Ponce"
date: "2025-02-27"
categories: ["SWDchallenge", "Exercise", "Data Visualization", "R Programming", "2025"]
tags: [
  "Market Analysis", "Strategic Positioning", "Dumbbell Chart", "Quadrant Chart", "ggplot2", "Competitive Analysis", "Market Share", "Category Coverage", "patchwork", "Business Intelligence", "Tidyverse", "Data Storytelling"
]
image: "thumbnails/swd_2025_02-Ex_0055.png"
format:
  html:
    toc: true
    toc-depth: 5
    code-link: true
    code-fold: true
    code-tools: true
    code-summary: "Show code"
    self-contained: true
editor_options: 
  chunk_output_type: inline
execute: 
  freeze: true                                          
  cache: true                                                   
  error: false
  message: false
  warning: false
  eval: true
# share:
#   permalink: "https://stevenponce.netlify.app/data_visualizations/SWD Challenge/2025/swd_2025_02-Ex_055.html" 
#   description: "Visualizing TrueNut's 70% market dominance in the $386M powdered nut butter market through quadrant and dumbbell charts #DataVisualization #MarketAnalysis #ggplot2"
#   linkedin: true
#   twitter: true
#   email: true
---

### Original

The goal of this month's Storytelling with Data exercise is to go crazy or keep it simple (marimekko chart).

![Original chart](https://stwd-prod-static-back.s3.amazonaws.com/media/Exercise_banners.png){#fig-1}


Additional information can be found [HERE](https://community.storytellingwithdata.com/exercises/go-crazy-or-keep-it-simple)


### Makeover

![A dual-panel visualization titled TrueNut's Market Dominance in Powdered Nut Butter. The left panel shows a quadrant chart positioning TrueNut as a market leader with 100% category coverage and high market share, NutBrite as a specialist with high coverage but lower share, and GoldenSpread as a limited player with low coverage and share. The right panel displays a dumbbell chart showing sales by product category, with TrueNut leading in most categories, particularly in Peanut Butter ($71.9M) and Hazelnut Spread ($73.81M).](swd_2025_02-Ex_0055.png){#fig-1}


### <mark> __Steps to Create this Graphic__ </mark>

#### 1. Load Packages & Setup 

```{r}
#| label: load

if (!require("pacman")) install.packages("pacman")
pacman::p_load(
  tidyverse,         # Easily Install and Load the 'Tidyverse'
  ggtext,            # Improved Text Rendering Support for 'ggplot2'
  showtext,          # Using Fonts More Easily in R Graphs
  scales,            # Scale Functions for Visualization
  glue,              # Interpreted String Literals
  here,              # A Simpler Way to Find Your Files
  janitor,           # Simple Tools for Examining and Cleaning Dirty Data
  camcorder,         # Record Your Plot History,
  paletteer,         # Comprehensive Collection of Color Palettes
  patchwork          # The Composer of Plots # The Composer of Plots # The Composer of Plots
) 

### |- figure size ---- 
gg_record( 
  dir    = here::here("temp_plots"), 
  device = "png",
  width  = 12,
  height = 8,
  units  = "in",
  dpi    = 320)
# Source utility functions
suppressMessages(source(here::here("R/utils/fonts.R")))
source(here::here("R/utils/social_icons.R"))
source(here::here("R/utils/image_utils.R"))
source(here::here("R/themes/base_theme.R"))
```

#### 2. Read in the Data 

```{r}
#| label: read

raw_data <- read_csv(
  here::here("data/swdexercise055.csv")
  ) |> 
  clean_names() 
```

#### 3. Examine the Data

```{r}
#| label: examine
#| include: true
#| eval: true
#| results: 'hide'
#| warning: false

glimpse(raw_data)
```

#### 4. Tidy Data 

```{r}
#| label: tidy

# P1. Market Share Data ----

# Transform raw data to long format with company identifiers
long_data <- raw_data |>
  # Remove the total market row
  filter(x1 != "Total Market") |>
  # Convert from wide to long format
  pivot_longer(
    cols = c(true_nut_sales, nut_brite_sales, golden_spread_sales),
    names_to = "company", 
    values_to = "sales"
  ) |>
  # Clean company names and calculate percentages
  mutate(
    # Use case_when instead of recode for better readability
    company = case_when(
      company == "true_nut_sales" ~ "TrueNut",
      company == "nut_brite_sales" ~ "NutBrite",
      company == "golden_spread_sales" ~ "GoldenSpread",
      TRUE ~ company  # Fallback for unexpected values
    ),
    # Calculate percentage of category total
    percentage = sales / total_sales * 100
  )

# Calculate overall company totals
company_totals <- raw_data |>
  # Sum sales for each company
  summarise(
    TrueNut = sum(true_nut_sales),
    NutBrite = sum(nut_brite_sales),
    GoldenSpread = sum(golden_spread_sales)
  ) |>
  # Convert to long format
  pivot_longer(
    cols = everything(), 
    names_to = "company", 
    values_to = "sales"
  ) |>
  # Calculate market share percentages
  mutate(percentage = sales / sum(sales) * 100)

# Calculate category coverage for each company
coverage <- long_data |>
  # Only count categories where the company has sales
  filter(sales > 0) |>
  # Count unique categories per company
  group_by(company) |>
  summarise(
    categories_covered = n_distinct(x1),
    # Calculate as percentage of all categories
    coverage_percent = categories_covered / n_distinct(long_data$x1) * 100
  )

# Join company totals with coverage data
position_data <- left_join(company_totals, coverage, by = "company")

# Define quadrant labels with semantic positioning
quadrant_labels <- tibble(
  # Define quadrant centers
  quadrant = c("Specialists", "Limited Players", "Market Leaders", "Volume Players"),
  x = c(25, 25, 75, 75),     
  y = c(65, 20, 65, 20),
  # Add clear descriptions
  description = c(
    "(Niche categories, strong coverage)",
    "(Low share, few categories)",
    "(Strong share, wide coverage)",
    "(High share, category focused)"
  )
) |>
  # Convert to label format expected by ggplot
  rename(label = quadrant)

# P2. Market by Product Category Data ----

# Calculate category statistics and establish ordering
category_stats <- long_data |> 
  # Group by category
  group_by(x1) |>
  # Get key category metrics (just once per category)
  summarize(
    total_sales = first(total_sales),
    .groups = "drop"  
  ) |>
  # Sort by total sales descending
  arrange(desc(total_sales))

# Create ordered factor for consistent category display
category_order <- category_stats |> pull(x1)

# Prepare the main plotting data with ordered categories
plot_data <- long_data |>
  # Create ordered factor with categories in descending sales order
  mutate(
    # Reverse for bottom-to-top ordering in the plot
    x1 = factor(x1, levels = rev(category_order))
  ) |>
  # Only include meaningful sales values
  filter(sales > 0.01)  

# Create the label data from filtered plot data
label_data <- plot_data |> 
  # Format sales values as currency with millions indicator
  mutate(
    label = paste0("$", round(sales, 2), "M")
  )

# Simplified category totals reference (using earlier calculation)
category_totals <- category_stats |>
  # Apply the same factor ordering
  mutate(x1 = factor(x1, levels = rev(category_order)))
```


#### 5. Visualization Parameters 

```{r}
#| label: params

### |-  plot aesthetics ----
# Get base colors with custom palette
colors <- get_theme_colors(
  palette = paletteer:::paletteer_d("ltc::trio4")
)

### |-  titles and caption ----
title_text <- str_glue("TrueNut's Market Dominance in Powdered Nut Butter")

subtitle_text <- str_glue("Strategic position and category performance across a $386M market")

# Create caption
caption_text <- create_swd_exe_caption(
  year = 2025,
  month = "Feb",
  source_text =  "Let's Practice! Exercise 5.5"
)

### |-  fonts ----
setup_fonts()
fonts <- get_font_families()

### |-  plot theme ----

# Start with base theme
base_theme <- create_base_theme(colors)

# Add weekly-specific theme elements
weekly_theme <- extend_weekly_theme(
  base_theme,
  theme(
    # Text styling 
    plot.title = element_text(face = "bold", family = fonts$title, size = rel(1.14), margin = margin(b = 10)),
    plot.subtitle = element_text(family = fonts$subtitle, color = colors$text, size = rel(0.78), margin = margin(b = 20)),
    
    # Axis elements
    axis.title = element_text(color = colors$text, size = rel(0.8)),
    axis.text = element_text(color = colors$text, size = rel(0.7)),
    
    # Grid elements
    panel.grid.minor = element_blank(),
    panel.grid.major = element_line(color = "grey95", linewidth = 0.1),
    
    # Legend elements
    legend.position = "plot",
    legend.title = element_text(family = fonts$text, size = rel(0.8)),
    legend.text = element_text(family = fonts$text, size = rel(0.7)),
    
    # Plot margins 
    plot.margin = margin(t = 10, r = 10, b = 10, l = 10),
  )
)

# Set theme
theme_set(weekly_theme)
```


#### 6. Plot

```{r}
#| label: plot

# P1. Market Position Chart ----
p1 <- ggplot(position_data, aes(x = percentage, y = coverage_percent)) +
  # Geoms
  geom_rect(xmin = 0, xmax = 50, ymin = 0, ymax = 50, fill = "gray95", alpha = 0.5) +
  geom_rect(xmin = 50, xmax = 100, ymin = 0, ymax = 50, fill = "gray95", alpha = 0.5) +
  geom_rect(xmin = 0, xmax = 50, ymin = 50, ymax = 100, fill = "gray95", alpha = 0.5) +
  geom_rect(xmin = 50, xmax = 100, ymin = 50, ymax = 100, fill = "gray95", alpha = 0.5) +
  geom_hline(yintercept = 50, linetype = "dashed", color = "gray50") +
  geom_vline(xintercept = 50, linetype = "dashed", color = "gray50") +
  geom_point(aes(color = company), size = 6, alpha = 0.8) +
  geom_text(aes(label = company), color = "gray20", 
            fontface = "bold", size = 3.5, vjust = 3) +
  geom_text(
    data = quadrant_labels,
    aes(x = x, y = y, label = label),
    size = 4,
    color = "gray40",
    fontface = "bold",
    hjust = 0.5,
    vjust = 0.5
  ) +
  geom_text(
    data = quadrant_labels,
    aes(x = x, y = y - 4, label = description),  
    size = 3,
    color = "gray50",
    fontface = "italic",
    hjust = 0.5,
    vjust = 0.5
  ) +
  # Scales
  scale_x_continuous(
    labels = percent_format(scale = 1), limits = c(0, 100)
    ) +
  scale_y_continuous(
    labels = percent_format(scale = 1), limits = c(0, 100)
    ) +
  scale_color_manual(values = colors$palette) +
  coord_cartesian(clip = "off") +
  # Labs  
  labs(
    title = "Company Market Position Analysis",
    subtitle = "Comparison of market share vs. category coverage",
    x = "Market Share\n(% of total sales)",
    y = "Category Coverage\n(% of product categories)"
  ) 

# P2. Market by Product Category Chart ----
p2 <- ggplot() +
  # Geoms
  geom_segment(
    data = category_totals,
    aes(y = x1, yend = x1, x = 0, xend = max(total_sales) * 1.05),
    color = "gray85", linewidth = 0.5
  ) +
  geom_point(
    data = plot_data,
    aes(x = sales, y = x1, color = company),
    size = 4, alpha = 0.9,
    show.legend = TRUE
  ) +
  geom_text(
    data = label_data,
    aes(x = sales, y = x1, label = label),
    vjust = -0.9, 
    size = 3,
    show.legend = FALSE  
  ) +
  geom_text(
    data = category_totals,
    aes(x = max(total_sales) * 1.1, y = x1, label = paste0("Total: $", total_sales, "M")),
    hjust = 0, vjust = 0.3, size = 3, color = "gray30",
    show.legend = FALSE
  ) +
  # Scales
  scale_x_continuous(
    labels = dollar_format(suffix = "M"),
    limits = c(-10, max(category_totals$total_sales) * 1.3),
    expand = c(0.01, 0)
  ) +
  scale_color_manual(values = colors$palette) +
  # Labs
  labs(
    title = "Nut Butter Market Analysis by Product Category",
    subtitle = "Sales comparison across product categories by company",
    x = "Sales ($ Millions)",
    y = NULL,
  ) +
  # Theme
  theme(
    legend.position = "top",
    legend.title = element_blank(),
    legend.key.size = unit(0.8, "lines"),  
  )

# Combined Charts -----

# Combined Plot -----
combined_plot <- (p1 + p2) +
  plot_layout(widths = c(1, 1))   

combined_plot <- combined_plot +
  plot_annotation(
    title = title_text,
    subtitle = subtitle_text,
    caption = caption_text,
    theme = theme(
      plot.title = element_text( 
        size = rel(2.2),
        family = fonts$title,
        face = "bold",
        color = colors$title,
        lineheight = 1.1,
        margin = margin(t = 5, b = 5)
      ),
      plot.subtitle = element_text(
        size = rel(0.95),  
        family = fonts$subtitle,
        color = colors$subtitle,
        lineheight = 1.2,
        margin = margin(t = 5, b = 10)
      ),
      plot.caption = element_markdown(
        size   = rel(0.65),
        family = fonts$caption,
        color  = colors$caption,
        hjust  = 0.5,
        margin = margin(t = 10)
      ),
      plot.margin = margin(t = 20, r = 10, b = 20, l = 10),
    ))
```

#### 7. Save

```{r}
#| label: save

### |-  plot image ----  

source(here::here("R/image_utils.R"))
save_plot_patchwork(
  combined_plot, type = 'swd', year = 2025, month = 02, exercise = 055,
  width = 12, height = 8
  )

```


#### 8. Session Info

::: {.callout-tip collapse="true"}
##### Expand for Session Info

```{r, echo = FALSE}
#| eval: true
#| warning: false

sessionInfo()
```
:::


#### 9. GitHub Repository
::: {.callout-tip collapse="true"}
##### Expand for GitHub Repo
 
The complete code for this analysis is available in [`swd_2025_02-Ex_055.qmd`](https://github.com/poncest/personal-website/tree/master/data_visualizations/SWD%20Challenge/2025/swd_2025_02-Ex_055.qmd).
For the full repository, [click here](https://github.com/poncest/personal-website/).
:::

#### 10. References
::: {.callout-tip collapse="true"}
##### Expand for References
 
Data Sources:

1. Data Sources:

   - Storytelling with Data Exercise | go crazy or keep it simple: [Download the data](https://community.storytellingwithdata.com/exercises/go-crazy-or-keep-it-simple)


:::

© 2024 Steven Ponce

Source Issues